use super::*;
use anyhow::Context;
use image::{Rgb, RgbImage};
use serde::{Deserialize, Serialize};
use std::fs::File;
use std::io::Write;
use std::path::Path;
// #[derive(Serialize, Deserialize)]
// pub struct RampData {
//     x: usize,
//     y: usize,
//     t: u8,
// }

#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
pub struct Ra3MapOtherData {
    pub heights: Vec<Vec<i32>>,
}

#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
pub struct Ra3Map {
    pub length: usize,
    pub width: usize,
    pub player_list: Vec<(usize, usize)>,
    pub water_height: i32,
    #[serde(skip_serializing)]
    pub data: Ra3MapOtherData,
    pub objects: Vec<Object>,
}

impl Ra3Map {
    #[inline]
    pub fn set_ramp(&mut self, (x, y): (usize, usize), t: Ramp, gen: &MapGen) {
        let config = &gen.config;

        let k = if gen.base[x][y].is_land == LandOrWater::Land {
            (config.height_deff) as f64 / (config.block_size / 2) as f64
        } else {
            (gen.config.default_height - gen.config.water_block_height) as f64
                / (config.block_size / 2) as f64
        };

        let edit = {
            let mut vec = Vec::new();

            for x in 0..=config.block_size / 2 {
                vec.push((x as f64 * k) as i32);
            }

            vec
        };

        debug_assert!(t != Ramp::None, "The ramp here should not be none !");

        let startx = x * config.block_size;
        let starty = y * config.block_size;

        match t {
            Ramp::Up => {
                for (index, i) in (startx..startx + config.block_size / 2).rev().enumerate() {
                    for j in starty..starty + config.block_size {
                        self.data.heights[i][j] += edit[index];
                    }
                }
            }
            Ramp::Down => {
                for (index, i) in (startx + config.block_size / 2..startx + config.block_size)
                    .rev()
                    .rev()
                    .enumerate()
                {
                    for j in starty..starty + config.block_size {
                        self.data.heights[i][j] += edit[index];
                    }
                }
            }
            Ramp::Left => {
                for (index, j) in (starty..starty + config.block_size / 2).rev().enumerate() {
                    for i in startx..startx + config.block_size {
                        self.data.heights[i][j] += edit[index];
                    }
                }
            }
            Ramp::Right => {
                for (index, j) in
                    (starty + config.block_size / 2..starty + config.block_size).enumerate()
                {
                    for i in startx..startx + config.block_size {
                        self.data.heights[i][j] += edit[index];
                    }
                }
            }
            Ramp::None => panic!(),
        };
    }
    #[inline]
    pub fn save<P: AsRef<Path>>(&self, dir_path: P) -> anyhow::Result<()> {
        use std::io::BufWriter;

        let mut other_data = self.data.clone();
        for (i, element) in other_data.heights.iter_mut().enumerate() {
            for (j, element) in element.iter_mut().enumerate() {
                *element = self.data.heights[j][i];
            }
        }

        let dir_path = dir_path.as_ref().to_path_buf();

        let mut data_path = dir_path.clone();
        data_path.push("data.json");

        let mut info_path = dir_path.clone();
        info_path.push("info.json");

        let data_file = File::create(data_path)?;
        let buff_writer = BufWriter::new(data_file);
        serde_json::to_writer(buff_writer, &other_data)?;

        let info_file = File::create(info_path)?;
        let buff_writer = BufWriter::new(info_file);
        serde_json::to_writer(buff_writer, self)?;

        let mut object_path = dir_path.clone();
        object_path.push("objects.txt");
        let mut object_file = File::create(object_path)?;
        let mut object_content = String::new();
        for (i, (x, y)) in self.player_list.iter().enumerate().map(|(i, p)| (i + 1, p)) {
            object_content.push_str(&format!("start,{},{},{}\n", y, x, i));
        }
        object_file.write(object_content.as_bytes())?;

        {
            let mut debug_content = String::new();
            for (_i, element) in self.data.heights.iter().enumerate() {
                for (_j, element) in element.iter().enumerate() {
                    debug_content.push_str(&format!("{}", element));
                    if *element < 10 {
                        debug_content.push_str("  ")
                    } else if *element < 100 {
                        debug_content.push_str(" ");
                    }
                    debug_content.push(' ');
                }
                debug_content.push('\n');
            }
            let mut debug_path = dir_path.clone();
            debug_path.push("debug2.txt");

            let mut debug_file = File::create(&debug_path)?;
            debug_file.write(debug_content.as_bytes())?;
        }

        let mut image_buff = RgbImage::new(self.length as u32, self.width as u32);
        for (i, element) in self.data.heights.iter().enumerate() {
            for (j, element) in element.iter().enumerate() {
                image_buff.put_pixel(j as u32, i as u32, Rgb([0, 0, *element as u8]));
            }
        }

        let mut image_path = dir_path.clone();
        image_path.push("data.png");

        image_buff.save(&image_path)?;

        Ok(())
    }
}

impl MapGen {
    #[inline]
    pub fn base_parse(&mut self) {
        let (m, n) = self.base_size();
        let block_size = self.config.block_size;

        let mut players = Vec::new();
        let mut heightsf = {
            let mut vec = Vec::new();
            vec.resize_with(m * block_size, || {
                let mut vec = Vec::new();
                vec.resize(n * block_size, 0);
                vec
            });
            vec
        };

        for (x, element) in self.base.iter().enumerate() {
            for (y, element) in element.iter().enumerate() {
                let startx = x * block_size;
                let starty = y * block_size;
                if element.is_land == LandOrWater::Land {
                    for i in 0..block_size {
                        for j in 0..block_size {
                            heightsf[startx + i][starty + j] = element.height as i32
                                * self.config.height_deff
                                + self.config.default_height;
                        }
                    }
                } else {
                    for i in 0..block_size {
                        for j in 0..block_size {
                            heightsf[startx + i][starty + j] = self.config.water_block_height;
                        }
                    }
                }

                if element.captured == Captured::PlayerSpawn {
                    players.push((
                        m * block_size - startx - block_size / 2,
                        starty + block_size / 2,
                    ));
                }
            }
        }

        let mut parsed = Ra3Map {
            width: n * self.config.block_size,
            length: m * self.config.block_size,
            player_list: players,
            water_height: self.config.water_height,
            data: Ra3MapOtherData { heights: heightsf },
            objects: Vec::new(),
        };

        for (i, element) in self.base.iter().enumerate() {
            for (j, element) in element.iter().enumerate() {
                if element.ramp != Ramp::None {
                    parsed.set_ramp((i, j), element.ramp, self)
                }
            }
        }

        self.parsed = Some(parsed);
    }
    #[inline]
    pub fn save_parse<P: AsRef<Path> + Clone>(&self, path: P) -> anyhow::Result<()> {
        match self.parsed.as_ref() {
            Some(val) => {
                val.save(path.clone())?;
            }
            None => {
                panic!()
            }
        }

        #[cfg(debug_assertions)]
        {
            let mut debug_content = String::new();

            for i in self.base.iter() {
                for j in i.iter() {
                    let str = match j.captured {
                        Captured::None => {
                            if j.is_land == LandOrWater::Land {
                                debug_content.push_str(&format!("{}", j.height));
                            } else {
                                debug_content.push('o');
                            }
                            continue;
                        }
                        Captured::PlayerSpawn => "P",
                        Captured::Ramp => match j.ramp {
                            Ramp::Up => "^",
                            Ramp::Down => "V",
                            Ramp::Left => "<",
                            Ramp::Right => ">",
                            Ramp::None => panic!("The ramp should not be none !"),
                        },
                    };
                    debug_content.push_str(str);
                }
                debug_content.push('\n');
            }

            debug_content.push('\n');

            for i in self.base.iter() {
                for j in i.iter() {
                    if j.is_land == LandOrWater::Land {
                        debug_content.push_str(&format!("{}", j.height));
                    } else {
                        debug_content.push('o');
                    }
                }
                debug_content.push('\n');
            }

            let mut debug_path = path.as_ref().to_path_buf();
            debug_path.push("debug.txt");

            let mut debug_file = File::create(&debug_path)?;
            debug_file.write(debug_content.as_bytes())?;
        }

        Ok(())
    }
}
